<?php

namespace WenRuns\Laravel\Admin\Grid\Exporters;

use App\Admin\Exporter\BaseExporter;
use Encore\Admin\Facades\Admin;
use Encore\Admin\Grid;
use Encore\Admin\Grid\Exporter;
use Encore\Admin\Grid\Exporters\ExcelExporter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Maatwebsite\Excel\Concerns\WithCustomChunkSize;
use Maatwebsite\Excel\Concerns\WithMapping;
use WenRuns\Laravel\Admin\Grid\Column;
use WenRuns\Laravel\Admin\Grid\Tools\Selector;

class WenExporter extends ExcelExporter implements WithCustomChunkSize, WithMapping
{
    /**
     * @var \WenRuns\Laravel\Admin\Grid|Grid
     */
    protected $grid;
    protected $columnsMapDBRaw = [];


    protected $primaryKey = null;

    protected $chunkSize;

    /**
     * @var null
     */
    protected $queryBuilder = null;

    /**
     * @var array
     */
    protected $columnsAlias = [];


    /**
     * 导出权限
     * @var mixed|null $permission
     */
    protected $permission = null;


    /**
     * 是否开启自适应字段导出
     * true--开启，如果没有设置columns时，会自动根据columns_selector导出可视字段
     * false--关闭， 不做任何处理
     * @var bool
     */
    protected $adaptiveFields = true;


    /**
     * 表格显示的字段Column
     * @var array
     */
    protected $columnInstance = [];


    /**
     * 表属性字段
     * @var array
     */
    protected $tableFields = [];


    /**
     * 获取表属性字段
     * @return array
     */
    public function getTableFields()
    {
        if (empty($this->tableFields)) {
            $this->tableFields = Schema::getColumnListing($this->getTable());
        }
        return $this->tableFields;
    }

    /**
     * @param Grid|null $grid
     */
    public function __construct(Grid $grid = null, $permission = null)
    {
        $this->chunkSize = config('excel.exports.chunk_size', 100);
        if ($permission) {
            $this->permission = $permission;
        }
        parent::__construct($grid);
    }


    /**
     * 权限判断
     * @return bool
     */
    public function hasPermission()
    {
        return empty($this->permission) || Admin::user()->can($this->permission);
    }

    /**
     * @return string
     */
    public function setUpScripts()
    {
        return '';
    }


    /**
     * @param $name
     * @return Column|null
     */
    private function getColumnInstance($name)
    {
        return $this->columnInstance[$name] ?? null;
    }

    /**
     * @param Grid $grid
     * @return $this|WenExporter
     */
    public function setGrid(Grid $grid)
    {
        parent::setGrid($grid);
        return $this->checkExportColumns();
    }


    /**
     * @return int[]|string[]
     */
    public function fields(): array
    {
        return array_keys(array_filter($this->columns, function ($value) {
            return !empty($value);
        }));
    }

    /**
     * @param mixed $row
     * @return array
     */
    public function map($row): array
    {
        if (empty($this->columnInstance)) {
            if ($row instanceof Model) {
                $row = $row->toArray();
            }
            return $row;
        }

        if (!($row instanceof Model)) {
            $row = (new \ReflectionClass($this->grid->model()->getOriginalModel()))
                ->newInstance()->fillable(array_keys($row))->fill($row);
        }
        Column::setOriginalGridModels(collect([$row]));
        $arr = [];
        foreach ($this->fields() as $name) {
            /**  @param Grid\Column $column */
            $column = $this->getColumnInstance($name);
            if ($column) {
                $index = $column->getName();
                $value = $column->callDisplayCallbacks($row->$index ?? null, 0);
            } else {
                $index = $this->columnsMapDBRaw[$name] ?? $name;
                $value = $row->$index ?? null;
            }
            $value = strip_tags($value);
            $method = preg_replace('/\.|_|\r|\n|\t/m', ' ', $index);
            $method = 'map' . preg_replace('/\s+/m', '', ucwords($method)) . 'Format';
            if (method_exists($this, $method)) {
                $value = $this->$method($value, $index, $row);
            }
            $arr[] = $value;
        }
        return $arr;
    }

    /**
     * @return int
     */
    public function chunkSize(): int
    {
        // TODO: Implement chunkSize() method.
        return $this->chunkSize;
    }

    /**
     *
     * @param $column
     * @return string
     */
    private function columnSelectName($column)
    {
        $name = $column->getName();
        if (!empty($select = $this->grid->model()->getQueryBuilder()->getQuery()->columns)) {
            if (in_array($name, $select)) {
                return $name;
            }
            foreach ($select as $item) {
                if ($item instanceof Expression) {
                    $item = $item->getValue();
                }
                $item = preg_replace('/(\r\n|\n|\t|\r)\s+/m', ' ', $item);
                $preg = '/(.+?)(\s+|\.)' . $name . '$/';
                if (preg_match($preg, $item, $res)) {
                    if ($res[2] == '.') {
                        return $res[0];
                    } else {
                        $_name = trim(preg_replace('/\s+(as|As|AS|aS)\s*$/', '', $res[1]));
                        $this->columnsMapDBRaw[$_name] = $name;
                        return $_name;
                    }
                }
            }
        }
        return Str::contains($name, '.') ? $name : $this->getTable() . '.' . $name;
    }

    /**
     * 检查导出字段
     * @return $this
     * @throws \Exception
     */
    protected function checkExportColumns(): WenExporter
    {
        if ($this->adaptiveFields || empty($this->columns)) {
            $__columns = $this->grid->resortColumns()->visibleColumnNames();
            foreach ($this->grid->getColumns() as $column) {
                if ($this->adaptiveFields &&
                    ((empty($__columns) && in_array($column->getName(), $this->grid->hiddenColumns)) ||
                        (!empty($__columns) && !in_array($column->getName(), $__columns)))) {
                    continue;
                }
                $name = $this->columnSelectName($column);
                if ($this->columnsAlias[$name] ?? null) {
                    $name = $this->columnsAlias[$name];
                }
                if (isset($this->columns[$name])) {
                    continue;
                }
                $this->columns[$name] = $column->getLabel();
                $this->columnInstance[$name] = $column;
            }
        }
        return $this;
    }


    /**
     * 查询导出数据
     * @return Builder|\Illuminate\Database\Eloquent\Model
     */
    public function query()
    {
        $query = $this->getQuery();
        if (!empty($columns = array_keys($this->columns))) {
            $eagerLoads = array_keys($this->getQuery()->getEagerLoads());
            $queryColumns = array_map(function ($item) {
                if ($item instanceof Expression) {
                    return $item->getValue();
                }
                return $item;
            }, $query->getQuery()->columns ?? []);
            $columns = collect($columns)->reject(function ($column) use ($eagerLoads, $queryColumns) {
                if (in_array($this->getTable() . '.*', $queryColumns) && strpos($column, $this->getTable() . '.') === 0) {
                    return true;
                }
                if (count(array_filter($queryColumns, function ($item) use ($column) {
                        return strpos(preg_replace("/\s*\n\s*/", ' ', $item), $column) === 0;
                    })) > 0) {
                    return true;
                }
                return in_array($column, $eagerLoads)
                    || preg_match('/(^|\.)' . implode('\.|(^|\.)', $eagerLoads) . '\./', $column)
                    || in_array($column, $queryColumns)
                    || (strpos($column, $this->getTable() . '.') === 0 && !in_array(str_replace($this->getTable() . '.', '', $column), $this->getTableFields()));
            });
            $columns = $columns->toArray();
            if (!empty($columns)) {
                if (!empty($this->columnsMapDBRaw)) {
                    foreach ($this->columnsMapDBRaw as $key => $item) {
                        $index = array_search($key, $columns);
                        if ($index === false) {
                            continue;
                        }
                        if (empty($item)) {
                            $columns[$index] = DB::raw($columns[$index]);
                            continue;
                        }
                        $columns[$index] = DB::raw($columns[$index] . ' as ' . $item);
                    }
                }
                $query->addSelect($columns);
            }
            if (method_exists($this, 'select') && !empty($selectColumns = $this->select($query))) {
                $query->addSelect($selectColumns);
            }
        }
        return $query;
    }

    /**
     * @param bool $toArray
     * @return array|\Illuminate\Support\Collection|mixed
     */
    public function getData($toArray = true)
    {
        $filter = $this->grid->getFilter();;
        $this->filter($filter->getModel());
        return $filter->execute($toArray);
    }


    /**
     * 筛选过滤
     * @param Grid\Model $builder
     */
    protected function filter(Grid\Model $builder)
    {
        // 字段筛选没有执行
        foreach ($this->grid->getColumns() as $column) {
            /** @var Grid\Column\Filter $filter */
            $filter = $column->filter;

            if ($filter) {
                $filter->addBinding(request($column->getName()), $builder);
            }
        }
        /** @var Selector $selectors */
        $selectors = $this->grid->selector();
        if ($selectors) {
            foreach ($selectors->getSelectors() as $key => $selector) {
                $query = $selector['query'] ?? null;
                if (is_callable($query)) {
                    $query->call($this, $builder);
                } else if (method_exists($this, $key . 'Selector')) {
                    $method = $key . 'Selector';
                    $this->$method($builder);
                }
            }
        }

    }


    /**
     * @return Builder|\Illuminate\Database\Eloquent\Model
     */
    public function getQuery()
    {
        if ($this->queryBuilder === null) {
            $model = $this->grid->getFilter()->getModel();
            $this->filter($model);

            $queryBuilder = $model->getQueryBuilder();


            // Export data of giving page number.
            if ($this->page) {
                $this->chunkSize = $perPage = request($model->getPerPageName(), $model->getPerPage());
                $queryBuilder->limit($perPage)->offset(($this->page - 1) * $perPage);
            }

            $this->queryBuilder = $queryBuilder;
        }
        return $this->queryBuilder;
    }

    /**
     * EXPORT DATA WITH SCOPE
     * @param string $scope
     * @return $this|BaseExporter
     */
    public function withScope($scope)
    {
        if ($scope == Exporter::SCOPE_ALL) {
            return $this;
        }
        list($scope, $args) = explode(':', $scope);
        if ($scope == Exporter::SCOPE_CURRENT_PAGE) {
            $this->grid->model()->usePaginate(true);
            $this->page = $args ?: 1;
        }

        if ($scope == Exporter::SCOPE_SELECTED_ROWS) {
            $selected = explode(',', $args);
            if (method_exists($this, 'selectedRows')) {
                $this->selectedRows($this->grid->model(), $selected);
            } else {
                $this->grid->model()->whereIn($this->getTable() . '.' . $this->grid->getKeyName(), $selected);
            }
        }

        return $this;
    }
}
